#include "graphics.h"

SDL_Texture *background_texture;

void init_graphics(SDL_Window **window, SDL_Renderer **renderer, SDL_Texture **display, TTF_Font **font, bool *render_change, button_node **root, button_node **tail) {
    background_texture = NULL;
    *render_change = 0;

    *window = SDL_CreateWindow(
        WINDOW_TITLE,
        /* initial pos of window */
        SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
        /* window scale */
        WINDOW_W, WINDOW_H,
        /* show immediately, highdpi, resizability */
        SDL_WINDOW_SHOWN | SDL_WINDOW_ALLOW_HIGHDPI
        //SDL_WINDOW_SHOWN | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_RESIZABLE
        /* TODO: fix window resizing */
    );

    if (*window == NULL) {
        fprintf(stderr, "SDL_CreateWindow error: %s\n", SDL_GetError());
        exit(EXIT_FAILURE);
    }

    /* creates renderer
       -1 specifies the loading of whichever video driver supports our flags
       SDL_RENDERER_ACCELERATED:  use hardware accelerating
       SDL_RENDERER_PRESENTVSYNC: synchronize present function (update display) with monitor refresh rate
    */
    *renderer = SDL_CreateRenderer(*window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_TARGETTEXTURE);

    if (*renderer == NULL) {
        fprintf(stderr, "SDL_CreateRenderer error: %s\n", SDL_GetError());
        exit(-1);
    }

    SDL_SetRenderDrawBlendMode(*renderer, SDL_BLENDMODE_BLEND);

    /* texture for render context */
    *display = SDL_CreateTexture(*renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, WINDOW_W, WINDOW_H);

    /* font to use */
    *font = NULL;
    /* try different font locations */
    if (*font == NULL)
        *font = TTF_OpenFont("/usr/share/fonts/TTF/mplus-1m-regular.ttf", FONTSIZE);
    if (*font == NULL)
        *font = TTF_OpenFont("/usr/share/fonts/TTF/Inconsolata-Regular.ttf", FONTSIZE);
    if (*font == NULL)
        *font = TTF_OpenFont("./src/font/mplus-1m-regular.ttf", FONTSIZE);
    if (*font == NULL) {
        *font = TTF_OpenFont("./src/font/Inconsolata-Regular.ttf", FONTSIZE);
    if (*font == NULL)
        fprintf(stderr,
                "\nTTF_OpenFont Error:  %s\n",
                SDL_GetError());
        exit(EXIT_FAILURE);
    }

    SDL_SetRenderTarget(*renderer, *display);

    /* group together all the button definitions */
    init_buttons(root, tail);
}

void set_render_change(bool *render_change) {
    *render_change = true;
}

void init_buttons(button_node **root, button_node **tail) {
    register_mousedown_event(
        root,
        tail,
        construct_button(
            BUTTON_TOGGLE_STATUS,
            BORDER_W,
            BORDER_H,
            WINDOW_W * 0.25,
            WINDOW_H * 0.1,
            &toggle_status,
            NULL
        )
    );
}

void pre_render(SDL_Renderer **renderer, SDL_Texture **display) {
    SDL_SetRenderTarget(*renderer, *display);
}

void update_render(SDL_Renderer **renderer, SDL_Texture **display, bool *render_change) {
    if (*render_change) {
        SDL_SetRenderTarget(*renderer, NULL);
        SDL_RenderCopy(*renderer, *display, NULL, NULL);

        SDL_RenderPresent(*renderer);
        *render_change = false;
    }
}

void cleanup_graphics(SDL_Window **window, SDL_Renderer **renderer) {
    SDL_DestroyRenderer(*renderer);
    SDL_DestroyWindow(*window);
}

void draw_frames(SDL_Renderer **renderer, TTF_Font **font, bool *render_change, int frame) {
    /* create framerate string to draw */
    /* string length according to digits */
    char str[frame / 10 - (frame % 10) + 1];
    sprintf(str, "frame %d", frame);
    draw_text_and_bg(renderer, font, render_change, -BORDER_W, BORDER_H + FONTHEIGHT, str, 0xffffff88);
}

static void draw_cover_art_wrapped(SDL_Renderer *renderer, unsigned char *image, int image_size) {
    if (image_size == 0 || image == NULL)
        return;

    if (DEBUG)
        printf("commence drawing of image\n");

    SDL_Surface *result = NULL;
    SDL_RWops *stream = SDL_RWFromConstMem(image, image_size);
    if (stream == NULL) {
        // error handling for failure - call SDL_GetError()
        fprintf(stderr, "stream NULL: %s\n", SDL_GetError());
        return;
    } else {
        if (IMG_isPNG(stream))
            result = IMG_LoadPNG_RW(stream);
        else if (IMG_isJPG(stream))
            result = IMG_LoadJPG_RW(stream);
        else {
            /* special case needs pre-emptive exit */
            printf("neither a png nor jpg!\n");
            if (background_texture != NULL) {
                SDL_DestroyTexture(background_texture);
                background_texture = NULL;
            }
            SDL_RWclose(stream);
            if (image != NULL)
                free(image);
            return;
        }
        SDL_RWclose(stream);
    }

    if (result == NULL) {
        fprintf(stderr, "surface NULL\n");
        return;
    }

    SDL_Texture* image_texture = SDL_CreateTextureFromSurface(renderer, result);

    if (image_texture == NULL) {
        fprintf(stderr,
            "\nSDL_CreateTextureFromSurface Error: %s\n",
            SDL_GetError());
        exit(EXIT_FAILURE);
    }

    if (background_texture != NULL)
        SDL_DestroyTexture(background_texture);
    background_texture = image_texture;

    free(image);
    SDL_FreeSurface(result);
}

void draw_cover_art(SDL_Renderer **renderer, bool *render_change, track *cr_track) {
    unsigned char *image = NULL;
    int image_size = 0;
    /* read id3 */
    read_image(cr_track->file, &image, &image_size);
    /* proceed to draw */
    if (image_size > 0 && image != NULL)
        draw_cover_art_wrapped(*renderer, image, image_size);

    set_render_change(render_change);
}

void draw_info(SDL_Renderer **renderer, TTF_Font **font, bool *render_change, track *cr_track, player *cr_player) {
    /* create framerate string to draw */
    /* string length according to digits */
    if (cr_track->title_tag != NULL) {
        draw_text_and_bg(renderer, font, render_change, BORDER_W, -BORDER_H, cr_track->title_tag, 0x11111166);
    }
    if (cr_track->artist_tag != NULL) {
        draw_text_and_bg(renderer, font, render_change, BORDER_W, -BORDER_H - FONTHEIGHT, cr_track->artist_tag, 0x11111166);
    }
    if (cr_track->album_tag != NULL) {
        draw_text_and_bg(renderer, font, render_change, BORDER_W, -BORDER_H - 3 * FONTHEIGHT, cr_track->album_tag, 0x11111166);
    }
    //draw_text_and_bg(BORDER_W, BORDER_H , cr_player->playing ? "status: playing" : "status: paused", 0x11111166);
    draw_text_and_bg(renderer, font, render_change, BORDER_W, BORDER_H , cr_player->status, 0x11111166);

    if (cr_track->duration > 0 && cr_player->position < cr_track->duration) {
        /* doesn't support hour long songs lol */
        char time_str[3 + 2 * 5];
        sprintf(
            time_str,
            "%.2d:%.2d / %.2d:%.2d",
            /* minutes, keep it 2 digits */
            ((cr_player->position / 60) % 100),
            (cr_player->position % 60),
            /* minutes, keep it 2 digits */
            ((cr_track->duration / 60) % 100),
            (cr_track->duration % 6)
        );
        draw_text_and_bg(renderer, font, render_change, -BORDER_W, BORDER_H, time_str, 0xffffff55);
    }
}

void draw_buttons(SDL_Renderer **renderer, bool *render_change, button_node *root) {
    /* iterate all buttons */
    button_node *node = root;
    while (node != NULL) {
        button_node *current = node;
        node = node->next;

        /* skip non-drawable buttons */
        if (current->payload.texture == NULL)
            continue;
        
        /* render at button dimensions */
        SDL_Rect render_quad = { current->payload.x, current->payload.y, current->payload.w, current->payload.h };
        SDL_RenderCopy(*renderer, current->payload.texture, NULL, &render_quad);
        set_render_change(render_change);
    }
}

void draw_bg(SDL_Renderer **renderer, bool *render_change, uint32_t color) {
    SDL_RenderClear(*renderer);

    if (background_texture == NULL) {
        SDL_SetRenderDrawColor(*renderer, partr(color), partg(color), partb(color), parta(color));
        draw_rect(renderer, render_change, 0, 0, WINDOW_W, WINDOW_H);
    } else {
        int w = 0, h = 0, offset_w = 0, offset_h = 0;
        SDL_QueryTexture(background_texture, NULL, NULL, &w, &h);
        /* if we need to scale down */
        if (w > WINDOW_W || h > WINDOW_H) {
            float ratio_w = (float) w / (float) WINDOW_W;
            float ratio_h = (float) h / (float) WINDOW_H;
            /* we decide to follow the less extreme conversion, that */
            /* is to say the lesser ratio */
            if (ratio_w > ratio_h) {
                w /= ratio_h;
                h /= ratio_h;
            } else {
                w /= ratio_w;
                h /= ratio_w;
            }
        /* if we need to scale up */
        } else if (w < WINDOW_W && h < WINDOW_H) {
            float ratio_w = (float) WINDOW_W / (float) w;
            float ratio_h = (float) WINDOW_H / (float) h;
            if (ratio_h > ratio_w) {
                w *= ratio_h;
                h *= ratio_h;
            } else {
                w *= ratio_w;
                h *= ratio_w;
            }
        }

        /* if a side is still larget than the max, we need to offset the */
        /* image to effectively center it */
        if (w > WINDOW_W)
            offset_w = -((w - WINDOW_W) / 2);
        if (h > WINDOW_H)
            offset_h = -((h - WINDOW_H) / 2);

        /* render image */
        SDL_Rect render_quad = { offset_w, offset_h, w, h };
        SDL_RenderCopy(*renderer, background_texture, NULL, &render_quad);
    }
    set_render_change(render_change);
}

void draw_rect(SDL_Renderer **renderer, bool *render_change, int x, int y, int w, int h) {
    SDL_Rect bg = { x, y, w, h };
    SDL_RenderDrawRect(*renderer, &bg);
    set_render_change(render_change);
}

static void draw_text_with_bg(SDL_Renderer **renderer, TTF_Font **font, bool *render_change, int x, int y, char *text, uint32_t color, bool with_bg, bool clip) {
    if (text == NULL)
        return;

    SDL_Color text_rgb = { partr(color), partg(color), partb(color) };

    /* ugliest code so far lol */
    int w, h, available_w = WINDOW_W - BORDER_W;
    bool duped = false;
    TTF_SizeUTF8(*font, text, &w, &h);
    if (clip && w > available_w) {
        int text_len = strlen(text);
        int avg_width = w / text_len;
        int cut_count = 0;
        duped = true;
        text = strdup(text);

        while (w > available_w) {
            w -= avg_width;
            ++cut_count;
        }

        if (text_len - 4 - cut_count > 0) {
            text[text_len - 4 - cut_count] = '.';
            text[text_len - 3 - cut_count] = '.';
        }
        if (text_len - 2 - cut_count > 0)
            text[text_len - 2 - cut_count] = '\0';
    }

    SDL_Surface* text_surface = TTF_RenderUTF8_Blended(*font, text, text_rgb);

    /* if previously duped, happens during clipping */
    if (duped)
        free(text);
    
    if (text_surface == NULL) {
        fprintf(stderr,
                "\nTTF_RenderText_Solid Error:  %s\n",
                SDL_GetError());
        exit(EXIT_FAILURE);
    }

    SDL_Texture* text_texture = SDL_CreateTextureFromSurface(*renderer, text_surface);

    if (text_texture == NULL) {
        fprintf(stderr,
                "\nSDL_CreateTextureFromSurface Error:  %s\n",
                SDL_GetError());
        exit(EXIT_FAILURE);
    }

    int message_w = text_surface->w;
    int message_h = text_surface->h;

    if (with_bg) {
        /* background time */
        uint32_t anti_color = (0xffffffff - color);
        SDL_SetRenderDrawColor(*renderer, partr(anti_color), partg(anti_color), partb(anti_color), parta(anti_color));
        SDL_Rect render_quad1 = { posx(x, message_w), posy(y, message_h), message_w, message_h };
        SDL_RenderFillRect(*renderer, &render_quad1);
    }

    /* text time */
    SDL_Rect render_quad2 = { posx(x, message_w), posy(y, message_h), message_w, message_h };
    SDL_RenderCopy(*renderer, text_texture, NULL, &render_quad2);

    SDL_FreeSurface(text_surface);
    SDL_DestroyTexture(text_texture);

    set_render_change(render_change);
}

void draw_text_and_bg(SDL_Renderer **renderer, TTF_Font **font, bool *render_change, int x, int y, char *text, uint32_t color) {
    draw_text_with_bg(renderer, font, render_change, x, y, text, color, true, true);
}

void draw_text(SDL_Renderer **renderer, TTF_Font **font, bool *render_change, int x, int y, char *text, uint32_t color) {
    draw_text_with_bg(renderer, font, render_change, x, y, text, color, false, true);
}

void draw_img(SDL_Renderer **renderer, SDL_Texture **display, bool *render_change, char *file, SDL_Rect* dstrect) {
    *display = IMG_LoadTexture(*renderer, file);
    if (*display == NULL) {
        fprintf(stderr, "draw_img error: %s\n", SDL_GetError());
        exit(EXIT_FAILURE);
    }
    if (SDL_SetTextureBlendMode(*display, SDL_BLENDMODE_BLEND)) {
        fprintf(stderr, "draw_img error: %s\n", SDL_GetError());
        exit(EXIT_FAILURE);
    }

    SDL_Rect srcrect = { 0, 0, WINDOW_W, WINDOW_H };
    SDL_RenderCopy(*renderer, *display, &srcrect, dstrect);
    set_render_change(render_change);
}

/* extract rgba part from uint32_t */
int partr(uint32_t color) {
    return (color & 0xFF000000) >> 24;
}

int partg(uint32_t color) {
    return (color & 0x00FF0000) >> 16;
}

int partb(uint32_t color) {
    return (color & 0x0000FF00) >> 8;
}

int parta(uint32_t color) {
    return (color & 0x000000FF) >> 0;
}

/* calculate position of texture to be placed */
/* negative numbers automatically offset from the opposite side */
int posx(int x, int w) {
    if (x < 0)
        return WINDOW_W - w + x;
    else
        return x;
}

int posy(int y, int h) {
    if (y < 0)
        return WINDOW_H - h + y;
    else
        return y;
}